﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using MathematicallySafe.Armourer;
using MathematicallySafe.Armourer.Base;
using MathematicallySafe.HeadCounter.Data.Common;
using MathematicallySafe.HeadCounter.Data.Parsers;
using StructureMap;

namespace MathematicallySafe.HeadCounter.Data.Models
{
    [Serializable]
    public class Guild
    {
        private Options _options = ObjectFactory.GetInstance<IHeadCounterService>().Options;
 
        private readonly IArmouryService _armouryService = ArmouryServiceFactory.Instance.GetArmouryService();
        private CharacterXmlParser _xmlParser = new CharacterXmlParser();

        private readonly object _sync = new object();

        public SerializableSortedList<string, Raider> Roster;

        public Guild()
        {
            Name = "<Guild>";
            Roster = new SerializableSortedList<string, Raider>();
        }

        public Guild(string name)
        {
            Name = name;
            Roster = new SerializableSortedList<string, Raider>();
        }

        public void SubscribeEvents()
        {
            _armouryService.CharacterDownloaded += ArmouryServiceCharacterDownloaded;
        }

        public void UnsubscribeEvents()
        {
            _armouryService.CharacterDownloaded -= ArmouryServiceCharacterDownloaded;
        }


        [XmlAttribute]
        public string Name { get; set; }

        public int NumberOfRaiders
        {
            get
            {
                lock (_sync)
                {
                    return Roster.Count();
                }
            }
        }

        public IEnumerable<Raider> Raiders
        {
            get
            {
                IEnumerable<Raider> snapshot = null;

                lock (_sync)
                {
                    snapshot = (from r in Roster.Values
                                select r).ToList();
                }

                return snapshot;
            }
        }

        public IEnumerable<Raider> ActiveRaiders
        {
            get
            {
                IEnumerable<Raider> snapshot = null;

                lock (_sync)
                {
                    snapshot = (from r in Roster.Values
                                where r.IsInactive == false
                                select r).ToList();
                }

                return snapshot;
            }
        }

        public IEnumerable<Raider> RaidersByMain
        {
            get
            {
                IEnumerable<Raider> snapshot = null;

                lock (_sync)
                {
                    snapshot = (from r in Roster.Values
                                orderby r.MainName ascending
                                select r).ToList();
                }

                return snapshot;
            }
        }

        public IEnumerable<Raider> ActiveRaidersByMain
        {
            get
            {
                IEnumerable<Raider> snapshot = null;

                lock (_sync)
                {
                    snapshot = (from r in Roster.Values
                                where r.IsInactive == false
                                orderby r.MainName ascending
                                select r).ToList();
                }

                return snapshot;
            }
        }

        public void AddRaider(string characterName, CharacterClass characterClass, CharacterRace characterRace,
                              DateTime joined)
        {
            if (IsMember(characterName))
                throw new ArgumentException("Raider " + characterName + " is already on the roster.");

            var character = new Character
                            {
                                Name = characterName,
                                Class = characterClass,
                                Race = characterRace
                            };

            var raider = new Raider(character)
                         {
                             Joined = new DateTime(joined.Year, joined.Month, joined.Day)
                         };

            lock (_sync)
            {
                Roster.Add(raider.Identity, raider);
            }

            OnRaiderAdded(new RosterChangedEventArgs(raider));
            DownloadCharacterAsynch(_options.Region, _options.Realm, raider.MainName);
        }

        public void RemoveRaider(string characterName)
        {
            Raider raider = null;

            lock (_sync)
            {
                raider = DoFindRaider(characterName);

                if (raider != null)
                {
                    Roster.Remove(raider.Identity);
                }
            }

            if (raider != null) OnRaiderRemoved(new RosterChangedEventArgs(raider));
        }

        #region Metrics

        public double ShortPeriodLootCountMean { get; set; }
        public double ShortPeriodLootValueMean { get; set; }
        public double ShortPeriodLootRateMean { get; set; }
        public double ShortPeriodWeightedLootRateMean { get; set; }
        public double? ShortPeriodLootPriorityMean { get; set; }

        public double LongPeriodLootCountMean { get; set; }
        public double LongPeriodLootValueMean { get; set; }
        public double LongPeriodLootRateMean { get; set; }
        public double LongPeriodWeightedLootRateMean { get; set; }
        public double? LongPeriodLootPriorityMean { get; set; }

        public double LifetimeLootCountMean { get; set; }
        public double LifetimeLootValueMean { get; set; }
        public double LifetimeLootRateMean { get; set; }
        public double LifetimeWeightedLootRateMean { get; set; }
        public double? LifetimeLootPriorityMean { get; set; }

        public double LootRateStdDev { get; set; }
        public double LootRateLow{ get { return LifetimeLootRateMean - (2.0*LootRateStdDev);} }
        public double LootRateHigh { get { return LifetimeLootRateMean + (2.0*LootRateStdDev);} }

        public double WeightedLootRateStdDev { get; set; }
        public double WeightedLootRateLow { get { return LifetimeWeightedLootRateMean - (2.0*WeightedLootRateStdDev);} }
        public double WeightedLootRateHigh { get { return LifetimeWeightedLootRateMean + (2.0 * WeightedLootRateStdDev); } }

        #endregion

        #region Events

        public event EventHandler<RosterChangedEventArgs> RaiderAdded;
        public event EventHandler<RosterChangedEventArgs> RaiderRemoved;
        public event EventHandler<CharacterDownloadedEventArgs> CharacterDownloaded;

        protected virtual void OnRaiderAdded(RosterChangedEventArgs e)
        {
            if (RaiderAdded != null)
            {
                RaiderAdded(this, e);
            }
        }

        protected virtual void OnRaiderRemoved(RosterChangedEventArgs e)
        {
            if (RaiderRemoved != null)
            {
                RaiderRemoved(this, e);
            }
        }

        protected virtual void OnCharacterDownloaded(CharacterDownloadedEventArgs e)
        {
            if (CharacterDownloaded != null)
            {
                CharacterDownloaded(this, e);
            }
        }

        #endregion

        public bool IsMember(string name)
        {
            return FindRaider(name) != null;
        }

        public Raider FindRaider(string characterName)
        {
            lock (_sync)
            {
                return DoFindRaider(characterName);
            }
        }

        // These private DoXXXX functions assume are already locked.

        private Raider DoFindRaider(string characterName)
        {
            Raider raider = null;

            if (!Roster.TryGetValue(characterName, out raider))
            {
                raider = (Roster.Values.Where(r => r.HasCharacter(characterName))).FirstOrDefault();
            }

            return raider;
        }

        private Raider DoFindRaiderMain(string mainName)
        {
            return Roster.Values.Where(r => r.MainName == mainName).FirstOrDefault();
        }

        #region Armoury downloads

        public void DownloadCharacterAsynch(WowRegion region, string realm, string name)
        {
            lock (_sync)
            {
                // todo what about downloading armoury data for alts?
                var raider = DoFindRaiderMain(name);

                if (raider == null || !raider.HasDownloadedItems())
                {
                    _armouryService.EnqueueGetCharacter(region, realm, name);
                    _armouryService.RunArmouryServiceAsync();
                }
            }
        }

        void ArmouryServiceCharacterDownloaded(object sender, CharacterDataDownloadedEventArgs e)
        {
            string name;

            try
            {
                name = e.CharacterXml.Element("character").Attribute("name").Value;
            }
            catch (Exception)
            {
                throw new ArgumentException("Could not find character from downloaded XML");
            }

            lock (_sync)
            {
                var raider = DoFindRaiderMain(name);

                if (raider != null)
                {
                    _xmlParser.UpdateCharacter(raider, e.CharacterXml);
                    raider.GearItems.ForEach(gear => _armouryService.EnqueueGetItem(gear.ID));
                }
            }

            if (CharacterDownloaded != null)
            {
                CharacterDownloaded(this, new CharacterDownloadedEventArgs(name));
            }
        }

        #endregion
    }

    public class RosterChangedEventArgs : EventArgs
    {
        public readonly Raider Raider;

        public RosterChangedEventArgs(Raider raider)
        {
            Raider = raider;
        }
    }
}